package net.xdclass.service.impl;

import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.extern.slf4j.Slf4j;
import net.xdclass.config.RabbitMQConfig;
import net.xdclass.constant.RedisKey;
import net.xdclass.controller.request.TrafficPageRequest;
import net.xdclass.controller.request.UseTrafficRequest;
import net.xdclass.enums.BizCodeEnum;
import net.xdclass.enums.EventMessageType;
import net.xdclass.enums.TaskStateEnum;
import net.xdclass.exception.BizException;
import net.xdclass.feign.ProductFeignService;
import net.xdclass.feign.ShortLinkFeignService;
import net.xdclass.interceptor.LoginInterceptor;
import net.xdclass.manager.TrafficManager;
import net.xdclass.manager.TrafficTaskManager;
import net.xdclass.model.EventMessage;
import net.xdclass.model.LoginUser;
import net.xdclass.model.TrafficDO;
import net.xdclass.model.TrafficTaskDO;
import net.xdclass.service.TrafficService;
import net.xdclass.util.JsonData;
import net.xdclass.util.JsonUtil;
import net.xdclass.util.TimeUtil;
import net.xdclass.vo.ProductVO;
import net.xdclass.vo.TrafficVO;
import net.xdclass.vo.UserTrafficVO;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 小滴课堂,愿景：让技术不再难学
 *
 * @Description
 * @Author 二当家小D
 * @Remark 有问题直接联系我，源码-笔记-技术交流群
 * @Version 1.0
 **/

@Service
@Slf4j
public class TrafficServiceImpl implements TrafficService {

    @Autowired
    private TrafficManager trafficManager;

    @Autowired
    private ProductFeignService productFeignService;

    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;


    @Autowired
    private TrafficTaskManager trafficTaskManager;


    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private RabbitMQConfig rabbitMQConfig;


    @Autowired
    private ShortLinkFeignService shortLinkFeignService;

    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public void handleTrafficMessage(EventMessage eventMessage) {

        Long accountNo = eventMessage.getAccountNo();
        String messageType = eventMessage.getEventMessageType();
        if (EventMessageType.PRODUCT_ORDER_PAY.name().equalsIgnoreCase(messageType)) {

            // 订单已经支付，新增流量

            String content = eventMessage.getContent();
            Map<String, Object> orderInfoMap = JsonUtil.json2Obj(content, Map.class);

            // 还原订单商品信息
            String outTradeNo = (String) orderInfoMap.get("outTradeNo");
            Integer buyNum = (Integer) orderInfoMap.get("buyNum");
            String productStr = (String) orderInfoMap.get("product");
            ProductVO productVO = JsonUtil.json2Obj(productStr, ProductVO.class);
            log.info("商品信息:{}", productVO);


            // 流量包有效期
            LocalDateTime expiredDateTime = LocalDateTime.now().plusDays(productVO.getValidDay());
            Date date = Date.from(expiredDateTime.atZone(ZoneId.systemDefault()).toInstant());


            // 构建流量包对象
            TrafficDO trafficDO = TrafficDO.builder()
                    .accountNo(accountNo)
                    .dayLimit(productVO.getDayTimes() * buyNum)
                    .dayUsed(0)
                    .totalLimit(productVO.getTotalTimes())
                    .pluginType(productVO.getPluginType())
                    .level(productVO.getLevel())
                    .productId(productVO.getId())
                    .outTradeNo(outTradeNo)
                    .expiredDate(date).build();

            int rows = trafficManager.add(trafficDO);
            log.info("消费消息新增流量包:rows={},trafficDO={}", rows, trafficDO);

            // 新增流量包，应该删除这个key
            String totalTrafficTimesKey = String.format(RedisKey.DAY_TOTAL_TRAFFIC, accountNo);
            redisTemplate.delete(totalTrafficTimesKey);

        } else if (EventMessageType.TRAFFIC_FREE_INIT.name().equalsIgnoreCase(messageType)) {
            // 发放免费流量包
            Long productId = Long.valueOf(eventMessage.getBizId());

            JsonData jsonData = productFeignService.detail(productId);

            ProductVO productVO = jsonData.getData(new TypeReference<ProductVO>() {
            });
            // 构建流量包对象
            TrafficDO trafficDO = TrafficDO.builder()
                    .accountNo(accountNo)
                    .dayLimit(productVO.getDayTimes())
                    .dayUsed(0)
                    .totalLimit(productVO.getTotalTimes())
                    .pluginType(productVO.getPluginType())
                    .level(productVO.getLevel())
                    .productId(productVO.getId())
                    .outTradeNo("free_init")
                    .expiredDate(new Date())
                    .build();

            trafficManager.add(trafficDO);

        } else if (EventMessageType.TRAFFIC_USED.name().equalsIgnoreCase(messageType)) {
            // 流量包使用，检查是否成功使用
            // 检查task是否存在
            // 检查短链是否成功
            // 如果不成功，则恢复流量包
            // 删除task (也可以更新task状态，定时删除就行)

            Long trafficTaskId = Long.valueOf(eventMessage.getBizId());
            TrafficTaskDO trafficTaskDO = trafficTaskManager.findByIdAndAccountNo(trafficTaskId, accountNo);

            // 非空且锁定
            if (trafficTaskDO != null && trafficTaskDO.getLockState().equalsIgnoreCase(TaskStateEnum.LOCK.name())) {

                JsonData jsonData = shortLinkFeignService.check(trafficTaskDO.getBizId());

                if (jsonData.getCode() != 0) {

                    log.error("创建短链失败，流量包回滚");

                    String useDateStr = TimeUtil.format(trafficTaskDO.getGmtCreate(), "yyyy-MM-dd");

                    trafficManager.releaseUsedTimes(accountNo, trafficTaskDO.getTrafficId(), 1, useDateStr);

                    // 恢复流量包，应该删除这个key（也可以让这个key递增）
                    String totalTrafficTimesKey = String.format(RedisKey.DAY_TOTAL_TRAFFIC, accountNo);
                    redisTemplate.delete(totalTrafficTimesKey);

                }

                // 多种方式处理task，不立刻删除，可以更新状态，然后定时删除也行
                trafficTaskManager.deleteByIdAndAccountNo(trafficTaskId, accountNo);

            }


        }


    }

    @Override
    public Map<String, Object> pageAvailable(TrafficPageRequest request) {
        int size = request.getSize();
        int page = request.getPage();
        LoginUser loginUser = LoginInterceptor.threadLocal.get();

        IPage<TrafficDO> trafficDOIPage = trafficManager.pageAvailable(page, size, loginUser.getAccountNo());

        // 获取流量包列表
        List<TrafficDO> records = trafficDOIPage.getRecords();
        List<TrafficVO> trafficVOList = records.stream()
                .map(obj -> beanProcess(obj))
                .collect(Collectors.toList());

        Map<String, Object> pageMap = new HashMap<>();
        pageMap.put("total_record", trafficDOIPage.getTotal());
        pageMap.put("total_page", trafficDOIPage.getPages());
        pageMap.put("current_data", trafficVOList);

        return pageMap;
    }

    @Override
    public TrafficVO detail(long trafficId) {
        LoginUser loginUser = LoginInterceptor.threadLocal.get();
        TrafficDO trafficDO = trafficManager.findByIdAndAccountNo(trafficId, loginUser.getAccountNo());

        return beanProcess(trafficDO);
    }

    @Override
    public boolean deleteExpireTraffic() {
        return trafficManager.deleteExpireTraffic();
    }

    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    @Override
    public JsonData reduce(UseTrafficRequest trafficRequest) {

        Long accountNo = trafficRequest.getAccountNo();
        // 处理流量包，筛选出未更新流量包,当前使用的流量包
        UserTrafficVO userTrafficVO = processTrafficList(accountNo);
        log.info("今天可用总次数: {}, 当前使用流量包: {}", userTrafficVO.getDayTotalLeftTimes(), userTrafficVO.getCurrentTrafficDO());

        if (Objects.isNull(userTrafficVO.getCurrentTrafficDO())) {
            return JsonData.buildResult(BizCodeEnum.TRAFFIC_REDUCE_FAIL);
        }

        log.info("待更新流量包列表: {}", userTrafficVO.getUnUpdatedTrafficIds());
        if (!userTrafficVO.getUnUpdatedTrafficIds().isEmpty()) {
            int updateRows = trafficManager.batchUpdateUsedTimes(accountNo, userTrafficVO.getUnUpdatedTrafficIds());
            log.info("更新今日流量包: {}", updateRows);
        }

        // 先更新再扣减当前使用的流量包
        int rows = trafficManager.addDayUsedTimes(accountNo, userTrafficVO.getCurrentTrafficDO().getId(), 1);
        if (rows != 1) {
            throw new BizException(BizCodeEnum.TRAFFIC_REDUCE_FAIL);
        }

        TrafficTaskDO trafficTaskDO = TrafficTaskDO.builder()
                .accountNo(accountNo)
                .trafficId(userTrafficVO.getCurrentTrafficDO().getId())
                .bizId(trafficRequest.getBizId())
                .useTimes(1)
                .lockState(TaskStateEnum.LOCK.name())
                .build();
        int add = trafficTaskManager.add(trafficTaskDO);
        if (add != 1) {
            throw new BizException(BizCodeEnum.TRAFFIC_REDUCE_FAIL);
        }


        // 将该用户当天可用的流量次数放入redis，以供其他服务预扣减使用
        Integer leftSeconds = TimeUtil.getRemainSecondsOneDay(new Date());
        String totalKey = String.format(RedisKey.DAY_TOTAL_TRAFFIC, accountNo);
        // 减一是此次减去的流量次数
        redisTemplate.opsForValue().set(totalKey, userTrafficVO.getDayTotalLeftTimes() - 1,
                leftSeconds, TimeUnit.SECONDS);

        // 发送延迟消息,用于异常回滚
        EventMessage trafficEventMessage = EventMessage.builder()
                .accountNo(accountNo)
                .bizId(trafficTaskDO.getId() + "")
                .eventMessageType(EventMessageType.TRAFFIC_USED.name())
                .build();
        rabbitTemplate.convertAndSend(rabbitMQConfig.getTrafficEventExchange(),
                rabbitMQConfig.getTrafficReleaseDelayRoutingKey(),
                trafficEventMessage);

        return JsonData.buildSuccess();
    }

    private UserTrafficVO processTrafficList(Long accountNo) {
        // 全部流量包
        List<TrafficDO> trafficDOList = trafficManager.selectAvailableTraffics(accountNo);
        if (trafficDOList == null || trafficDOList.isEmpty()) {
            throw new BizException(BizCodeEnum.TRAFFIC_EXCEPTION);
        }
        // 天剩余可用总次数
        Integer dayTotalLeftTimes = 0;
        // 当前使用的流量包
        TrafficDO currentTrafficDO = null;
        // 没过期，但是今天没更新的流量包id
        List<Long> unUpdatedTrafficIds = new ArrayList<>();

        // 今天日期
        String todayStr = TimeUtil.format(new Date(), "yyyy-MM-dd");
        for (TrafficDO trafficDO : trafficDOList) {
            String trafficUpdateDate = TimeUtil.format(trafficDO.getGmtModified(), "yyyy-MM-dd");
            if (todayStr.equalsIgnoreCase(trafficUpdateDate)) {
                // 已更新
                int dayLeftTimes = trafficDO.getDayLimit() - trafficDO.getDayUsed();
                dayTotalLeftTimes += dayLeftTimes;

                // 选出当次使用的流量包
                if (dayLeftTimes > 0 && currentTrafficDO == null) {
                    currentTrafficDO = trafficDO;
                }

            } else {
                // 未更新
                dayTotalLeftTimes += trafficDO.getDayLimit();
                // 记录未更新流量包
                unUpdatedTrafficIds.add(trafficDO.getId());
                // 选出当次使用的流量包
                if (currentTrafficDO == null) {
                    currentTrafficDO = trafficDO;
                }
            }
        }

        UserTrafficVO userTrafficVO = new UserTrafficVO(dayTotalLeftTimes, currentTrafficDO, unUpdatedTrafficIds);

        return userTrafficVO;
    }

    private TrafficVO beanProcess(TrafficDO obj) {
        TrafficVO trafficVO = new TrafficVO();
        BeanUtils.copyProperties(obj, trafficVO);
        return trafficVO;
    }


}
